package com.norman.sorry.core.boards;

import java.awt.Color;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.norman.sorry.core.pieces.IPiece;
import com.norman.sorry.core.tiles.AbstractLinkedSlide;
import com.norman.sorry.core.tiles.AbstractLinkedTile;
import com.norman.sorry.core.tiles.ILinkedTile;
import com.norman.sorry.core.tiles.ISlideFactory;
import com.norman.sorry.core.tiles.ITileFactory;

/**
 * A factory class that provides methods to creating different types of game
 * boards.
 * 
 * @author Brian Norman
 * @version 0.0.1 beta
 * @since 0.0.1 beta
 */
public class BoardFactory {

   /**
    * The tile factory used for creating linked tiles.
    */
   private ITileFactory<? extends AbstractLinkedTile>   tiles;

   /**
    * The slide factory used for creating linked slides.
    */
   private ISlideFactory<? extends AbstractLinkedSlide> slides;

   /**
    * Creates a new board factory that uses the specified factories to create
    * tiles and slides. Both factories must produce a subclass of linked tiles
    * and slides respectively.
    * 
    * @param tileFactory
    *           factory for creating linked tiles.
    * @param slideFactory
    *           factory for creating linked slides.
    */
   public BoardFactory(ITileFactory<? extends AbstractLinkedTile> tileFactory,
         ISlideFactory<? extends AbstractLinkedSlide> slideFactory) {
      this.tiles = tileFactory;
      this.slides = slideFactory;
   }

   /**
    * Returns a new map of the starting tiles of a standard game board layout.
    * 
    * @return a map of the starting tiles.
    */
   public Map<Color, ILinkedTile> createStandard() {
      // create the four starting points
      AbstractLinkedTile startRed = tiles.createStart();
      AbstractLinkedTile startBlue = tiles.createStart();
      AbstractLinkedTile startYellow = tiles.createStart();
      AbstractLinkedTile startGreen = tiles.createStart();

      // create the four entrance points
      AbstractLinkedTile entranceRed = tiles.createTile();
      AbstractLinkedTile entranceBlue = tiles.createTile();
      AbstractLinkedTile entranceYellow = tiles.createTile();
      AbstractLinkedTile entranceGreen = tiles.createTile();

      // link start and entrance
      startRed.setNext(entranceRed);
      startBlue.setNext(entranceBlue);
      startYellow.setNext(entranceYellow);
      startGreen.setNext(entranceGreen);

      // create the first tile after the entrance
      AbstractLinkedTile iterRed = tiles.createTile();
      AbstractLinkedTile iterBlue = tiles.createTile();
      AbstractLinkedTile iterYellow = tiles.createTile();
      AbstractLinkedTile iterGreen = tiles.createTile();

      // link the entrance and the iterator
      entranceRed.setNext(iterRed);
      entranceBlue.setNext(iterBlue);
      entranceYellow.setNext(iterYellow);
      entranceGreen.setNext(iterGreen);
      iterRed.setPrevious(entranceRed);
      iterBlue.setPrevious(entranceBlue);
      iterYellow.setPrevious(entranceYellow);
      iterGreen.setPrevious(entranceGreen);

      // create the next 3 tiles before the first slide
      for (int i = 0; i < 3; i++) {
         AbstractLinkedTile tempRed = tiles.createTile();
         AbstractLinkedTile tempBlue = tiles.createTile();
         AbstractLinkedTile tempYellow = tiles.createTile();
         AbstractLinkedTile tempGreen = tiles.createTile();

         iterRed.setNext(tempRed);
         iterBlue.setNext(tempBlue);
         iterYellow.setNext(tempYellow);
         iterGreen.setNext(tempGreen);
         tempRed.setPrevious(iterRed);
         tempBlue.setPrevious(iterBlue);
         tempYellow.setPrevious(iterYellow);
         tempGreen.setPrevious(iterGreen);

         iterRed = tempRed;
         iterBlue = tempBlue;
         iterYellow = tempYellow;
         iterGreen = tempGreen;
      }

      // create the first slide
      AbstractLinkedSlide slideRed = slides.create(Color.RED);
      AbstractLinkedSlide slideBlue = slides.create(Color.BLUE);
      AbstractLinkedSlide slideYellow = slides.create(Color.YELLOW);
      AbstractLinkedSlide slideGreen = slides.create(Color.GREEN);

      // link the slide with the iterator
      iterRed.setNext(slideRed);
      iterBlue.setNext(slideBlue);
      iterYellow.setNext(slideYellow);
      iterGreen.setNext(slideGreen);
      slideRed.setPrevious(iterRed);
      slideBlue.setPrevious(iterBlue);
      slideYellow.setPrevious(iterYellow);
      slideGreen.setPrevious(iterGreen);

      // create the second tile in the slide
      iterRed = tiles.createTile();
      iterBlue = tiles.createTile();
      iterYellow = tiles.createTile();
      iterGreen = tiles.createTile();

      // add tile to the slide
      slideRed.add(iterRed);
      slideBlue.add(iterBlue);
      slideYellow.add(iterYellow);
      slideGreen.add(iterGreen);

      // link the tile to the slide
      slideRed.setNext(iterRed);
      slideBlue.setNext(iterBlue);
      slideYellow.setNext(iterYellow);
      slideGreen.setNext(iterGreen);
      iterRed.setPrevious(slideRed);
      iterBlue.setPrevious(slideBlue);
      iterYellow.setPrevious(slideYellow);
      iterGreen.setPrevious(slideGreen);

      // create the other 3 tiles in the slide
      for (int i = 0; i < 3; i++) {
         AbstractLinkedTile tempRed = tiles.createTile();
         AbstractLinkedTile tempBlue = tiles.createTile();
         AbstractLinkedTile tempYellow = tiles.createTile();
         AbstractLinkedTile tempGreen = tiles.createTile();

         slideRed.add(tempRed);
         slideBlue.add(tempBlue);
         slideYellow.add(tempYellow);
         slideGreen.add(tempGreen);

         iterRed.setNext(tempRed);
         iterBlue.setNext(tempBlue);
         iterYellow.setNext(tempYellow);
         iterGreen.setNext(tempGreen);
         tempRed.setPrevious(iterRed);
         tempBlue.setPrevious(iterBlue);
         tempYellow.setPrevious(iterYellow);
         tempGreen.setPrevious(iterGreen);

         iterRed = tempRed;
         iterBlue = tempBlue;
         iterYellow = tempYellow;
         iterGreen = tempGreen;
      }

      // create the 2 tiles after the slide until the slide
      for (int i = 0; i < 2; i++) {
         AbstractLinkedTile tempRed = tiles.createTile();
         AbstractLinkedTile tempBlue = tiles.createTile();
         AbstractLinkedTile tempYellow = tiles.createTile();
         AbstractLinkedTile tempGreen = tiles.createTile();

         slideRed.add(tempRed);
         slideBlue.add(tempBlue);
         slideYellow.add(tempYellow);
         slideGreen.add(tempGreen);

         iterRed.setNext(tempRed);
         iterBlue.setNext(tempBlue);
         iterYellow.setNext(tempYellow);
         iterGreen.setNext(tempGreen);
         tempRed.setPrevious(iterRed);
         tempBlue.setPrevious(iterBlue);
         tempYellow.setPrevious(iterYellow);
         tempGreen.setPrevious(iterGreen);

         iterRed = tempRed;
         iterBlue = tempBlue;
         iterYellow = tempYellow;
         iterGreen = tempGreen;
      }

      // create the second slide, on the next side of the board
      slideRed = slides.create(Color.BLUE);
      slideBlue = slides.create(Color.YELLOW);
      slideYellow = slides.create(Color.GREEN);
      slideGreen = slides.create(Color.RED);

      // link to the corner
      iterRed.setNext(slideRed);
      iterBlue.setNext(slideBlue);
      iterYellow.setNext(slideYellow);
      iterGreen.setNext(slideGreen);
      slideRed.setPrevious(iterRed);
      slideBlue.setPrevious(iterBlue);
      slideYellow.setPrevious(iterYellow);
      slideGreen.setPrevious(iterGreen);

      // create the switch tile before the gap and safe zone
      AbstractLinkedTile switchRed = tiles.createTile();
      AbstractLinkedTile switchBlue = tiles.createTile();
      AbstractLinkedTile switchYellow = tiles.createTile();
      AbstractLinkedTile switchGreen = tiles.createTile();

      // add to the slide
      slideRed.add(switchRed);
      slideBlue.add(switchBlue);
      slideYellow.add(switchYellow);
      slideGreen.add(switchGreen);

      // link to the slide
      slideRed.setNext(switchRed);
      slideBlue.setNext(switchBlue);
      slideYellow.setNext(switchYellow);
      slideGreen.setNext(switchGreen);
      switchRed.setPrevious(slideRed);
      switchBlue.setPrevious(slideBlue);
      switchYellow.setPrevious(slideYellow);
      switchGreen.setPrevious(slideGreen);

      // create the gap tile between the switch and the next entrance
      AbstractLinkedTile gapRed = tiles.createTile();
      AbstractLinkedTile gapBlue = tiles.createTile();
      AbstractLinkedTile gapYellow = tiles.createTile();
      AbstractLinkedTile gapGreen = tiles.createTile();

      // add to the slide
      slideRed.add(gapRed);
      slideBlue.add(gapBlue);
      slideYellow.add(gapYellow);
      slideGreen.add(gapGreen);

      // link to the switch
      switchRed.setNext(gapRed);
      switchBlue.setNext(gapBlue);
      switchYellow.setNext(gapYellow);
      switchGreen.setNext(gapGreen);
      gapRed.setPrevious(switchRed);
      gapBlue.setPrevious(switchBlue);
      gapYellow.setPrevious(switchYellow);
      gapGreen.setPrevious(switchGreen);

      // add the next entrance to the slide
      slideRed.add(entranceBlue);
      slideBlue.add(entranceYellow);
      slideYellow.add(entranceGreen);
      slideGreen.add(entranceRed);

      // link the gap and the next entrance
      gapRed.setNext(entranceBlue);
      gapBlue.setNext(entranceYellow);
      gapYellow.setNext(entranceGreen);
      gapGreen.setNext(entranceRed);
      entranceBlue.setPrevious(gapRed);
      entranceYellow.setPrevious(gapBlue);
      entranceGreen.setPrevious(gapYellow);
      entranceRed.setPrevious(gapGreen);

      // create the first tile in the safe zone
      iterRed = tiles.createSafeTile();
      iterBlue = tiles.createSafeTile();
      iterYellow = tiles.createSafeTile();
      iterGreen = tiles.createSafeTile();

      // link to the switch tile
      switchRed.setNext(Color.BLUE, iterRed);
      switchBlue.setNext(Color.YELLOW, iterBlue);
      switchYellow.setNext(Color.GREEN, iterYellow);
      switchGreen.setNext(Color.RED, iterGreen);
      iterRed.setPrevious(switchRed);
      iterBlue.setPrevious(switchBlue);
      iterYellow.setPrevious(switchYellow);
      iterGreen.setPrevious(switchGreen);

      // create the next 4 tiles in the safe zone
      for (int i = 0; i < 4; i++) {
         AbstractLinkedTile tempRed = tiles.createSafeTile();
         AbstractLinkedTile tempBlue = tiles.createSafeTile();
         AbstractLinkedTile tempYellow = tiles.createSafeTile();
         AbstractLinkedTile tempGreen = tiles.createSafeTile();

         iterRed.setNext(tempRed);
         iterBlue.setNext(tempBlue);
         iterYellow.setNext(tempYellow);
         iterGreen.setNext(tempGreen);
         tempRed.setPrevious(iterRed);
         tempBlue.setPrevious(iterBlue);
         tempYellow.setPrevious(iterYellow);
         tempGreen.setPrevious(iterGreen);

         iterRed = tempRed;
         iterBlue = tempBlue;
         iterYellow = tempYellow;
         iterGreen = tempGreen;
      }

      // create the home tile
      AbstractLinkedTile homeRed = tiles.createHome();
      AbstractLinkedTile homeBlue = tiles.createHome();
      AbstractLinkedTile homeYellow = tiles.createHome();
      AbstractLinkedTile homeGreen = tiles.createHome();

      // link to the safe zone
      iterRed.setNext(homeRed);
      iterBlue.setNext(homeBlue);
      iterYellow.setNext(homeYellow);
      iterGreen.setNext(homeGreen);
      homeRed.setPrevious(iterRed);
      homeBlue.setPrevious(iterBlue);
      homeYellow.setPrevious(iterYellow);
      homeGreen.setPrevious(iterGreen);

      // create the home map
      Map<Color, ILinkedTile> map = new HashMap<Color, ILinkedTile>();
      map.put(Color.RED, startRed);
      map.put(Color.BLUE, startBlue);
      map.put(Color.GREEN, startGreen);
      map.put(Color.YELLOW, startYellow);
      return map;
   }

   /**
    * Returns a new standard game board. This game board will be populated with
    * the specified pieces and use the standard game board design.
    * 
    * @param pieces
    *           the game pieces on the game board.
    * @return a new standard game board.
    */
   public AbstractBoard createStandard(Collection<IPiece> pieces) {
      return new Board(pieces, createStandard());
   }

   /**
    * Private concrete class for creating new instances of a game board.
    */
   private static final class Board extends AbstractBoard {
      public Board(Collection<IPiece> pieces, Map<Color, ILinkedTile> startMap) {
         super(pieces, startMap);
      }

   }

}
